<?php

/**
 * Class WsServer
 */
class WsServer
{
    CONST HOST = "127.0.0.1";
    CONST PORT = 9502;

    private $ws = null;

    public function __construct()
    {
        $this->ws = new Swoole\WebSocket\Server(self::HOST, self::PORT);

        $this->ws->set(
            [
                //'enable_static_handler' => true,
                //'document_root'         => "/data/www/pro/tp5-live/public/static",
                'worker_num' => 2, //指定启动的worker进程数, 设置为CPU的1-4倍最合理
                'max_request' => 10000, //每个worker进程允许处理的最大任务数
                'task_worker_num' => 2, //服务器开启的task进程数
                'task_max_request' => 1000, //每个task进程允许处理的最大任务数
                //'dispatch_mode'         => 3, //数据包分发策略 - 1:轮循模式, 2:固定模式, 3:抢占模式, 4:IP分配, 5:UID 分配
                'daemonize' => false, //守护进程(true/false)
            ]
        );

        // 注意：onStart/onManagerStart/onWorkerStart 回调是在不同进程中并行执行的，不存在先后顺序

        $this->ws->on("start", [$this, 'onStart']);
        $this->ws->on("managerStart", [$this, 'onManagerStart']);
        $this->ws->on("workerStart", [$this, 'onWorkerStart']);
        $this->ws->on("workerStop", [$this, 'onWorkerStop']);
        $this->ws->on("open", [$this, 'onOpen']);
        $this->ws->on("message", [$this, 'onMessage']);
        $this->ws->on("request", [$this, 'onRequest']);
        $this->ws->on("receive", [$this, 'onReceive']);
        $this->ws->on("task", [$this, 'onTask']);
        $this->ws->on("finish", [$this, 'onFinish']);
        $this->ws->on("close", [$this, 'onClose']);

        $this->ws->start();
    }

    /**
     * tip: onStart 事件在 Master 进程的主线程中被调用
     * @param $server
     */
    public function onStart($server)
    {
        echo '[' . date('Y-m-d H:i:s') . '] ' . "onStart" . PHP_EOL;
        echo 'master_pid: ' . $server->master_pid . ', manager_pid: ' . $server->manager_pid . PHP_EOL;
        // 设置master进程别名
        //swoole_set_process_name("live_master");
    }

    /**
     * 当管理进程启动时触发此事件
     * Manager 与 Master 并行
     * @param $server
     */
    public function onManagerStart($server)
    {
        echo '[' . date('Y-m-d H:i:s') . '] ' . "onManagerStart" . PHP_EOL;
        // 设置manager进程别名
        //swoole_set_process_name("live_manager");
    }

    /**
     * tip: 此事件在 Worker进程 / Task进程 启动时发生，这里创建的对象可以在进程生命周期内使用。
     * 如果想使用 Reload 机制实现代码重载入，必须在 onWorkerStart 中 require 业务文件, 每个进程都需要在内存中保存一份。
     * 在 onWorkerStart 调用之前已包含的文件，不会重新载入代码。
     * 可以将公用的、不易变的 php 文件放置到 onWorkerStart 之前。
     * 注意：发生致命错误或者代码中主动调用 exit 时，Worker/Task 进程会退出，可能导致死循环
     * @param $server
     * @param $worker_id
     * @throws Exception
     */
    public function onWorkerStart($server, $worker_id)
    {
        //可以通过 $server->taskworker 属性来判断当前是 Worker 进程还是 Task 进程
        if($worker_id >= $server->setting['worker_num']) { //Task进程
            //swoole_set_process_name("task worker");
            echo '[' . date('Y-m-d H:i:s') . '] ' . "onWorkerStart-task: $worker_id" . PHP_EOL;
        } else { //Worker进程
            //swoole_set_process_name("event worker");
            echo '[' . date('Y-m-d H:i:s') . '] ' . "onWorkerStart-worker: $worker_id" . PHP_EOL;
        }

        // 定义应用目录
        define('DS', DIRECTORY_SEPARATOR);
        define('ROOT', realpath('../') . DS);
        define('APP', ROOT . 'app');
        define('APP_MODULE', 'app');
        define('CORE', ROOT . 'core');

        // 引入自动加载
        require ROOT . '/vendor/autoload.php';

        // 自定义加载器
        //var_dump(file_get_contents(CORE . '/text.txt'));
        require_once CORE . '/Base.php';

        spl_autoload_register('\core\Base::load');
        \core\Base::run();
    }

    /**
     * Worker/Task 进程结束时会调用
     * @param $server
     * @param $worker_id
     * @return void
     */
    public function onWorkerStop($server, $worker_id)
    {
        echo '[' . date('Y-m-d H:i:s') . '] ' . "onWorkerStop: $worker_id" . PHP_EOL;
    }

    /**
     * 监听ws连接事件
     * @param $ws
     * @param $request
     */
    public function onOpen($ws, $request)
    {
        echo '[' . date('Y-m-d H:i:s') . '] ' . "onOpen" . PHP_EOL;
        //$this->ws->push($request->fd, "hello, welcome: " . $request->fd);
    }

    /**
     * request回调
     * @param $request
     * @param $response
     * @return false|string|void
     */
    public function onRequest($request, $response)
    {
        echo '[' . date('Y-m-d H:i:s') . '] ' . "onRequest" . PHP_EOL;

        if ($request->server['request_uri'] == '/favicon.ico') {
            $response->status(404);
            $response->end();
            return;
        }

        $_SERVER = [];
        if (isset($request->server)) {
            foreach ($request->server as $k => $v) {
                $_SERVER[strtoupper($k)] = $v;
            }
        }
        if (isset($request->header)) {
            foreach ($request->header as $k => $v) {
                $_SERVER[strtoupper($k)] = $v;
            }
        }

        $_GET = [];
        $_REQUEST = [];
        if (isset($request->get)) {
            foreach ($request->get as $k => $v) {
                $_GET[$k] = $v;
                $_REQUEST[$k] = $v;
            }
        }
        $_FILES = [];
        if (isset($request->files)) {
            foreach ($request->files as $k => $v) {
                $_FILES[$k] = $v;
            }
        }
        $_POST = [];
        if (isset($request->post)) {
            foreach ($request->post as $k => $v) {
                $_POST[$k] = $v;
                $_REQUEST[$k] = $v;
            }
        }

        // 传递Swoole server服务对象
        $_POST['http_server'] = $this->ws;

        ob_start();

        // 执行应用并响应
        try {
            //
        } catch (\Exception $e) {
            // todo
            echo '[' . date('Y-m-d H:i:s') . ']' . "Exception: {$e->getMessage()}\n";
        }

        $res = ob_get_contents();
        ob_end_clean();

        return $res;
    }

    /**
     * 监听ws消息事件
     * @param $ws
     * @param $frame
     */
    public function onMessage($ws, $frame)
    {
        echo '[' . date('Y-m-d H:i:s') . '] ' . "onMessage\n";
        $taskData = [
            'method' => 'send',
            'data' => [
                'from' => 'onMessage',
                'msg' => $frame->data
            ]
        ];
        $this->ws->task(json_encode($taskData));

        // 协程测试
        for ($i = 0; $i < 10; $i++) {
            go(function () use ($ws, $frame, $i) {
                $this->ws->push($frame->fd, $i);
                usleep(1000);
            });
        }

        $this->ws->push($frame->fd, $frame->data);
    }

    /**
     * 接收到数据时回调此函数，发生在 worker 进程中。
     * 单次收到的数据最大为 [package_max_length] || 64K
     * @param $server
     * @param $fd
     * @param $reactorId
     * @param $data
     * @return void
     */
    public function onReceive($server, $fd, $reactorId, $data)
    {
        echo '[' . date('Y-m-d H:i:s') . '] ' . "onReceive: " . $data . PHP_EOL;
        $this->ws->task($data);
    }

    /**
     * 在 task 进程内被调用。worker 进程可以使用 task 函数向 task_worker 进程投递新的任务。
     * 当前的 Task 进程在调用 onTask 回调函数时会将进程状态切换为忙碌，这时将不再接收新的 Task，
     * 当 onTask 函数返回时会将进程状态切换为空闲然后继续接收新的 Task。
     * @param $server
     * @param $taskId
     * @param $workerId
     * @param $data
     * @return bool
     */
    public function onTask($server, $taskId, $workerId, $data)
    {
        echo '[' . date('Y-m-d H:i:s') . '] ' . "onTask: $data" . PHP_EOL;

        // 分发 task 任务机制，让不同的任务 走不同的逻辑
        $obj = new app\common\business\SwooleTask;

        $param = json_decode($data, true);
        $method = $param['method'];
        $flag = $obj->$method($param['data']);

        app\common\lib\Helper::debugLog("$data result: {$flag}", 'ws-task');
        echo '[' . date('Y-m-d H:i:s') . '] ' . "onTask-{$method}: {$flag}" . PHP_EOL;

        //return 字符串，表示将此内容返回给 worker 进程。worker 进程中会触发 onFinish 函数，表示投递的 task 已完成;
        //当然也可以通过 Swoole\Server->finish() 来触发 onFinish 函数，而无需再 return
        return $flag;
    }

    /**
     * 此回调函数在worker进程被调用
     * 当worker进程投递的任务在task进程中完成时，task进程会通过 Swoole\Server->finish() 方法将任务处理的结果发送给 worker 进程。
     * @param $server
     * @param $taskId
     * @param $data
     */
    public function onFinish($server, $taskId, $data)
    {
        echo '[' . date('Y-m-d H:i:s') . '] ' . "onFinish - {$taskId}: {$data}" . PHP_EOL;
    }

    /**
     * close
     * @param $ws
     * @param $fd
     */
    public function onClose($ws, $fd)
    {
        echo '[' . date('Y-m-d H:i:s') . '] ' . "onClose - fd: {$fd}\n";
    }

    public function runStart() {

    }
}

// nohup php /data/www/xxx/server/WsServer.php > /data/www/xxx/runtime/log/ws.log &
new WsServer();